/*!\file ffdecoder.cpp
*/

#include "StdAfx.h"
#include "qafdebug.h"
#include "seException.h"
#include ".\ffdecoder.h"

//!A class to init all the format and codec
/*!
\author axboris@gmail.com
\date [12/26/2006]
*/
class _AVInit
{
public:
	_AVInit()
	{
		av_register_all();
	}
	~_AVInit()
	{
		av_free_static();
	}
};
static _AVInit __singleton;


CFFDecoder::CFFDecoder(void): m_nAudioSampleRate(44100)
,m_nAudioBitRate(64000),m_nFrameRate(25), m_nFrameRateBase(1)
,m_nFrameWidth(0), m_nFrameHeight(0),m_fFrameAspectRatio(0.0f)
,m_nFramePadLeft(0), m_nFramePadRight(0), m_nFramePadButtom(0), m_nFramePadTop(0)
,m_szVideoStandard("ntsc"),m_nVideoBitRate(200*1000),m_nVideoBitRateTolerance(4000*1000)
,m_FramePixFmt(PIX_FMT_NONE), m_audioCodecID(CODEC_ID_NONE)
,m_pIFileFormat(NULL), m_pOFileFormat(NULL),m_pImageFormat(NULL)
,m_bGenpts(false), m_bVideoStreamCopy(false), m_bAudioStreamCopy(false),m_bIntraOnly(false), m_bSameQuality(false)
,m_nThreadCount(2),m_szAudioLang(NULL),m_nVideoGlobalHeader(0)
,m_nGopSize(12),m_fVideoQscale(0.0f),m_pInterMatirx(NULL), m_pIntraMatrix(NULL)
,m_nVideoQMin(2), m_nVideoQMax(31), m_nVideoLMin(2*FF_QP2LAMBDA), m_nVideoLMax(31*FF_QP2LAMBDA)
,m_nVideoMbLmax(31*FF_QP2LAMBDA),m_nVideoMbLMin(2*FF_QP2LAMBDA)
,m_nVideoQdiff(3), m_fVideoQblur(0.5f),m_fVideoQsquish(0.0f),m_fVideoQcomp(0.5f)
,m_szVideoRcOverrideString(NULL),m_szVideoRcEq("tex^qComp"),m_nVideoRcBufferSize(0),m_fVideoRcBufferAggressivity(1.0f)
,m_nVideoRcMaxRate(0), m_nVideoRcMinRate(0),m_fVideoRcInitialCplx(0.0f)
,m_fVideoBQfactor(1.25f),m_fVideoBQoffset(1.25f),m_fVideoIQfactor(-0.8f),m_fVideoIQoffset(0.0f)
,m_nVideoIntraQuantBias(FF_DEFAULT_QUANT_BIAS), m_nvideoInterQuantBias(FF_DEFAULT_QUANT_BIAS)
,m_nMeMethod(ME_EPZS),m_bVideoDisable(false),m_bVideoDiscard(false)
,m_videoCodecID(CODEC_ID_NONE),m_nSubtitleCodecID(CODEC_ID_NONE),m_szSubtitleLang(NULL)
,m_nBFrames(0),m_nPreME(0),m_nDoDeinterlace(0),m_nWorkAroundBugs(FF_BUG_AUTODETECT)
,m_nPacketSize(0),m_nErrorRate(0),m_nStrict(0),m_nTopFieldFirst(-1)
,m_nScThreshold(0),m_nMbThreshold(0)
,m_nIntraDcPreision(8),m_nMePenaltyCompsensation(256),m_nFrameSkipThreshold(0)
,m_nFrameSkipFactor(0),m_nFrameSkipExp(0),m_nLoopOutput(AVFMT_NOOUTPUTLOOP),m_nQpHist(0)
,m_fAudioQscale(-99999),m_nAudioChannels(1)
,m_nMuxRate(0),m_nMuxPacketSize(0),m_fMuxPreload(0.5f),m_fMuxMaxDelay(0.7f)
,m_nRecordTime(0),m_n64StartTime(0),m_nRecTimeStamp(0),m_nInputTsOffset(0)
,m_nFileOverWrite(0),m_szTitle(NULL),m_szAuthor(NULL),m_szCopyRight(NULL),m_szComment(NULL)
,m_nDoBenchMark(0),m_nDoHexDump(0),m_nDoPktDump(0),m_nDoPSNR(0),m_nDoVstats(0),m_nDoPass(0)
,m_szPassLogFileName(NULL),m_bSubtitleStreamCopy(0)
,m_nVideoSyncMethod(1),m_nAudioSyncMethod(0),m_nCopyTS(0),m_nOptShortest(0)
,m_nRateEmu(0),m_szVideoGrabFormat("video4linux")
,m_szVideoDevice(NULL),m_szGrabDevice(NULL),m_nVideoChannels(NULL)
,m_szAudioGrabFormat("audio_device"),m_szAudioDevice(NULL),m_nAudioVolume(256),m_nUsingStdin(0)
,m_nUsingVhook(0),m_nVerbose(1),m_nQPressed(0),m_nMeRange(0)
,m_nVideoSize(0),m_nAudioSize(0),m_nExtraSize(0),m_nNbFramesDup(0)
,m_nNbFramesDrop(0),m_nInputSync(0),m_nLimitFileSize(0)
,m_nPgmyuvCompatibiltyHack(0),m_nDtsDeltaThreshold(10),m_szOptName(NULL)
,m_nOptNameCount(0)
{
	m_pAvctxOpts = avcodec_alloc_context();//FIXME
	//av_register_all();
}

CFFDecoder::~CFFDecoder(void)
{
	//av_free_static();
}

int CFFDecoder::PutMedia(const char* szFileName)
{
	if(Q_INVALID(!szFileName)){
		return fferr_arg;
	}	
	if(!url_exist(szFileName)){
        return fferr_io;
	}
	m_infile = szFileName;

	AVFormatContext *ic = NULL;
	AVFormatParameters params, *ap = &params;
	int err, i, ret, rfps, rfps_base;
	int64_t timestamp;

	memset(ap, 0, sizeof(*ap));
	ap->sample_rate = m_nAudioSampleRate;
	ap->channels = m_nAudioChannels;
	ap->time_base.den = m_nFrameRate;
	ap->time_base.num = m_nFrameRateBase;
	ap->width = m_nFrameWidth + m_nFramePadLeft + m_nFramePadRight;
	ap->height = m_nFrameHeight + m_nFramePadTop + m_nFramePadButtom;
	ap->image_format = m_pImageFormat;
	ap->pix_fmt = m_FramePixFmt;
	ap->device  = NULL;
	ap->channel = m_nVideoChannels;
	ap->standard = m_szVideoStandard;
	ap->video_codec_id = m_videoCodecID;
	ap->audio_codec_id = m_audioCodecID;

	ret = av_open_input_file(&ic, szFileName, m_pIFileFormat, 0, ap);
	if(ret < 0){
		return fferr_file;
	}
	if(m_bGenpts){
		ic->flags |= AVFMT_FLAG_GENPTS;
	}
	ret = av_find_stream_info(ic);
	if(ret < 0){
		return fferr_codec_para;
	}
	timestamp = m_n64StartTime;
	if(ic->start_time != AV_NOPTS_VALUE){
		timestamp += ic->start_time;
	}
	if(m_n64StartTime != 0){ //if seek requested, we execute it
        ret = av_seek_frame(ic, -1, timestamp, AVSEEK_FLAG_BACKWARD);
		//if(ret < 0){			
		//}
		m_n64StartTime = 0;
	}

	for (i = 0; i < ic->nb_streams; i++){
		int j;
		AVCodecContext* enc = ic->streams[i]->codec;
		avcodec_thread_init(enc, m_nThreadCount);
		enc->thread_count = m_nThreadCount;
		switch(enc->codec_type)	
		{
		case CODEC_TYPE_AUDIO:
			m_nAudioChannels = enc->channels;
			m_nAudioSampleRate = enc->sample_rate;
			break;
		case CODEC_TYPE_VIDEO:
			m_nFrameWidth = enc->width;
			m_nFrameHeight = enc->height;
			m_fFrameAspectRatio = av_q2d(enc->sample_aspect_ratio) * enc->width / enc->height;
			m_FramePixFmt = enc->pix_fmt;
			rfps = ic->streams[i]->r_frame_rate.num;
			rfps_base = ic->streams[i]->r_frame_rate.den;
			enc->workaround_bugs = FF_BUG_AUTODETECT;
			if(enc->lowres) enc->flags |= CODEC_FLAG_EMU_EDGE;
			// me_threshold 
			if(enc->time_base.den != rfps || enc->time_base.num != rfps_base){
				//just warning 
			}
			m_nFrameRate = rfps;
			m_nFrameRateBase = rfps_base;
			
			enc->rate_emu = m_nRateEmu;
			break;
		case CODEC_TYPE_DATA:
			break;
		case CODEC_TYPE_SUBTITLE:
			break;
		case CODEC_TYPE_UNKNOWN:
			break;
		default:
			break;
        }//switch
	}
	m_pIC = ic;
	m_pIFileFormat = NULL;
	m_pOFileFormat = NULL;
	m_pImageFormat = NULL;
	m_nVideoChannels = 0;
	m_nRateEmu = 0;
	return ff_ok;     	

}


int CFFDecoder::OutPut(const char* filename, bool overwrite /* = true */)
{
	if(!filename){
		return fferr_arg;
	}
	if(url_exist(filename)&& !overwrite){
		return fferr_file;
	}
	m_outfile = filename;

	AVFormatContext *oc;
	int use_video, use_audio, input_has_video, input_has_audio;
	AVFormatParameters params, *ap = &params;

	oc = av_alloc_format_context();
	if(!m_pOFileFormat){
		m_pOFileFormat = guess_format(NULL, filename, NULL);
		if(!m_pOFileFormat){
			return fferr_fmt;
		}
	}
	oc->oformat = m_pOFileFormat;
	pstrcpy(oc->filename, sizeof(oc->filename), filename);

	use_video = m_pOFileFormat->video_codec != CODEC_ID_NONE || m_bVideoStreamCopy || m_videoCodecID != CODEC_ID_NONE;
	use_audio = m_pOFileFormat->audio_codec != CODEC_ID_NONE || m_bAudioStreamCopy || m_audioCodecID != CODEC_ID_NONE;   
    //FIXME  need more code to set use or not use video and audio.
	newVideoStream(oc);
	newAudioStream(oc);
	if(!oc->nb_streams){
		return fferr_mem;
	}
	oc->timestamp = m_nRecTimeStamp;
	if(m_szTitle) pstrcpy(oc->title, sizeof(oc->title), m_szTitle);
	if(m_szAuthor)pstrcpy(oc->author, sizeof(oc->author), m_szAuthor);
	if(m_szCopyRight)pstrcpy(oc->copyright, sizeof(oc->copyright), m_szCopyRight);
	if(m_szComment)pstrcpy(oc->comment, sizeof(oc->comment), m_szComment);
	
	memset(ap,0, sizeof(*ap));
	ap->image_format = m_pImageFormat;
	if(av_set_parameters(oc, ap) < 0){
		return fferr_arg;
	}
    
	if(url_fopen(&oc->pb, oc->filename, URL_WRONLY) < 0){
		return fferr_file;
	}
	oc->packet_size = m_nMuxPacketSize;
	oc->mux_rate = m_nMuxRate;
	oc->preload = (int)(m_fMuxPreload * AV_TIME_BASE);
	oc->max_delay = (int)(m_fMuxMaxDelay*AV_TIME_BASE);
	oc->loop_output = m_nLoopOutput;
	m_pOC = oc;

	m_pOFileFormat = NULL;
	m_pIFileFormat = NULL;
	m_pImageFormat = NULL;
}

int CFFDecoder::newAudioStream(AVFormatContext* oc)
{
	AVStream *st;
	AVCodecContext *audio_enc;
	int i;
	CodecID codec_id;

	st = av_new_stream(oc, oc->nb_streams);
	if(!st){
		return fferr_mem;
	}
	avcodec_thread_init(st->codec, m_nThreadCount);
	
	audio_enc = st->codec;
	audio_enc->codec_type = CODEC_TYPE_AUDIO;
	audio_enc->codec_tag = 0;//FIXME

	if (oc->oformat->flags & AVFMT_GLOBALHEADER){
		audio_enc->flags |= CODEC_FLAG_GLOBAL_HEADER;
		m_pAvctxOpts->flags |= CODEC_FLAG_GLOBAL_HEADER;
	}
	if(m_bAudioStreamCopy){
		st->stream_copy = 1;
		audio_enc->channels = m_nAudioChannels;
	}else{
		codec_id = av_guess_codec(oc->oformat, NULL, oc->filename, NULL, CODEC_TYPE_AUDIO);
	}
	if(m_audioCodecID != CODEC_ID_NONE){
		codec_id = m_audioCodecID;
	}

	audio_enc->codec_id = codec_id;
	audio_enc->bit_rate = m_nAudioBitRate;
	//FIXME qscale
	audio_enc->strict_std_compliance = 0;
	audio_enc->thread_count = m_nThreadCount;
	if(m_nAudioChannels > 2 && 
		m_audioCodecID != CODEC_ID_AC3 && m_audioCodecID != CODEC_ID_DTS)
	{
            m_nAudioChannels = 2;
	}
	if(m_szAudioLang){
		pstrcpy(st->language, sizeof(st->language), m_szAudioLang);
		av_free(m_szAudioLang);
		m_szAudioLang = NULL;
	}
	//reset some key parameters.
	m_audioCodecID = CODEC_ID_NONE;
	m_bAudioStreamCopy = false;
	return ff_ok;
}

int CFFDecoder::newVideoStream(AVFormatContext* oc)
{
	AVStream* st;
	AVCodecContext* video_enc;
	CodecID codec_id;

	st = av_new_stream(oc, oc->nb_streams);
	if(!st){
		return fferr_mem;
	}
	avcodec_thread_init(st->codec, m_nThreadCount);
	video_enc = st->codec;
	video_enc->codec_tag = 0;//FIXME
	if( (m_nVideoGlobalHeader &1) ||
		(0 == m_nVideoGlobalHeader && (oc->oformat->flags && AVFMT_GLOBALHEADER)))
	{
		video_enc->flags |= CODEC_FLAG_GLOBAL_HEADER;
		m_pAvctxOpts->flags |= CODEC_FLAG_GLOBAL_HEADER;        
	}
	if(m_nVideoGlobalHeader & 2){
		video_enc->flags2 |= CODEC_FLAG2_LOCAL_HEADER;
		m_pAvctxOpts->flags2 |= CODEC_FLAG2_LOCAL_HEADER;
	}

	if(m_bVideoStreamCopy){
		st->stream_copy = 1;
		video_enc->codec_type = CODEC_TYPE_VIDEO;
	}else{
		char* p = NULL;
		int i;
		AVCodec* codec = NULL;
		codec_id = av_guess_codec(oc->oformat, NULL, oc->filename, NULL, CODEC_TYPE_VIDEO);
		if(m_videoCodecID != CODEC_ID_NONE){
			codec_id = m_videoCodecID;
		}
		video_enc->codec_id = codec_id;
		codec = avcodec_find_encoder(codec_id);

		video_enc->bit_rate = m_nVideoBitRate;
		video_enc->bit_rate_tolerance = m_nVideoBitRateTolerance;
		video_enc->time_base.den = m_nFrameRate;
		video_enc->time_base.num = m_nFrameRateBase;
		if(codec && codec->supported_framerates){//FIXME what is this mean?
			const AVRational *p = codec->supported_framerates;
			AVRational req = {m_nFrameRate, m_nFrameRateBase};
			const AVRational* best = NULL;
			AVRational best_error = {INT_MAX, 1};
			for (; p->den != 0; p++)
			{
				AVRational error =av_sub_q(req, *p);
				if(error.num < 0) error.num *= -1;
				if(av_cmp_q(error, best_error) < 0){
					best_error = error;
					best = p;
				}
			}
			video_enc->time_base.den = best->num;
			video_enc->time_base.num = best->den;
		}	
		video_enc->width = m_nFrameWidth + m_nFramePadRight + m_nFramePadLeft;
		video_enc->height = m_nFrameHeight + m_nFramePadTop + m_nFramePadButtom;
		video_enc->sample_aspect_ratio = av_d2q(m_fFrameAspectRatio * m_nFrameHeight / m_nFrameWidth, 255);
		video_enc->pix_fmt = m_FramePixFmt;
		if(codec && codec->pix_fmts){
			const enum PixelFormat *p = codec->pix_fmts;
			for (; *p!=-1; p++)
			{
				if(*p == video_enc->pix_fmt)
					break;
			}
			if(*p == -1){
				video_enc->pix_fmt = codec->pix_fmts[0];
			}
		}
		if(!m_bIntraOnly){
			video_enc->gop_size = m_nGopSize;
		}else{
			video_enc->gop_size = 0;
		}
		if(m_fVideoQscale || m_bSameQuality){
			video_enc->flags |= CODEC_FLAG_QSCALE;
			video_enc->global_quality = st->quality = FF_QP2LAMBDA * m_fVideoQscale;
		}
		if(m_pIntraMatrix){
			video_enc->intra_matrix= m_pIntraMatrix;
		}
		if(m_pInterMatirx){
			video_enc->inter_matrix = m_pInterMatirx;
		}
		video_enc->pre_me = m_nPreME;
		if(m_nBFrames){
			video_enc->max_b_frames = m_nBFrames;
			video_enc->b_quant_factor = 2.0f;
		}
		video_enc->qmin = m_nVideoQMin;
		video_enc->qmax = m_nVideoQMax;
		video_enc->lmin = m_nVideoLMin;
		video_enc->lmax = m_nVideoLMax;
		video_enc->rc_qsquish = m_fVideoQsquish;
		video_enc->mb_lmax = m_nVideoMbLmax;
		video_enc->mb_lmin = m_nVideoMbLMin;
		video_enc->max_qdiff = m_nVideoQdiff;
		video_enc->qblur = m_fVideoQblur;
		video_enc->qcompress = m_fVideoQcomp;
		video_enc->rc_eq = m_szVideoRcEq;
		video_enc->workaround_bugs = m_nWorkAroundBugs;
		video_enc->thread_count = m_nThreadCount;
		p = m_szVideoRcOverrideString;
		for (i = 0; p; i++)
		{
			int start, end,q;
			int e = sscanf(p, "%d,%d,%d", &start, &end, &q);
			if( e!= 3){//FIXME
				return fferr_arg;
			}
			video_enc->rc_override = (RcOverride*)av_realloc(video_enc->rc_override, 
				sizeof(RcOverride)*(i+1));
			video_enc->rc_override[i].start_frame = start;
			video_enc->rc_override[i].end_frame = end;
			if(q > 0){
				video_enc->rc_override[i].qscale = q;
				video_enc->rc_override[i].quality_factor = 1.0f;
			}else{
				video_enc->rc_override[i].qscale = 0;
				video_enc->rc_override[i].quality_factor = -q/100.0f;
			}
			p = strchr(p,'/');
			if(p)p++;
		}
		video_enc->rc_override_count = i;
		video_enc->rc_max_rate = m_nVideoRcMaxRate;
		video_enc->rc_min_rate = m_nVideoRcMinRate;
		video_enc->rc_buffer_size = m_nVideoRcBufferSize;
		video_enc->rc_initial_buffer_occupancy = m_nVideoRcBufferSize * 3/4;
		video_enc->rc_buffer_aggressivity = m_fVideoRcBufferAggressivity;
		video_enc->rc_initial_cplx = m_fVideoRcInitialCplx;
		video_enc->i_quant_factor = m_fVideoIQfactor;
		video_enc->i_quant_offset = m_fVideoIQoffset;
		video_enc->b_quant_factor = m_fVideoBQfactor;
		video_enc->b_quant_offset = m_fVideoBQoffset;
		video_enc->intra_quant_bias = m_nVideoIntraQuantBias;
		video_enc->inter_quant_bias = m_nvideoInterQuantBias;
		video_enc->me_threshold = m_nMeThreshold;
		video_enc->mb_threshold = m_nMbThreshold;
		video_enc->intra_dc_precision = m_nIntraDcPreision - 8;
		video_enc->strict_std_compliance = m_nStrict;
		video_enc->error_rate = m_nErrorRate;
		video_enc->scenechange_threshold = m_nScThreshold;
		video_enc->me_range = m_nMeRange;
		video_enc->me_penalty_compensation = m_nMePenaltyCompsensation;
		video_enc->frame_skip_threshold = m_nFrameSkipThreshold;
		video_enc->frame_skip_factor = m_nFrameSkipFactor;
		video_enc->frame_skip_exp = m_nFrameSkipExp;

		if(m_nPacketSize){
			video_enc->rtp_mode = 1;
			video_enc->rtp_payload_size = m_nPacketSize;
		}

		if(m_nDoPSNR){
			video_enc->flags |= CODEC_FLAG_PSNR;
		}
		video_enc->me_method = m_nMeMethod;
		if(m_nDoPass){
			if(m_nDoPass == 1){
				video_enc->flags |= CODEC_FLAG_PASS1;
			}else{
				video_enc->flags |= CODEC_FLAG_PASS2;
			}
		}
	}//else

	//reset 
	m_videoCodecID = CODEC_ID_NONE;
	m_bVideoStreamCopy = false;
	m_bVideoDisable = false;
	return ff_ok;
}

int CFFDecoder::FFEncode()
{
	return 0;


}

int CFFDecoder::DecodeFile(char* input_filename)
{ 
	int err=0, i=0, counter=0, len1=0, got_picture, video_index=-1;
	

	AVFormatContext * fcx=NULL;
	AVCodecContext * ccx=NULL;
	AVCodec * codec=NULL;

	AVFormatParameters params;
	AVPicture pict;
	AVPacket pkt;
	AVFrame *frame=avcodec_alloc_frame();


	// Open the input file.
	err = av_open_input_file(&fcx, input_filename, NULL, 0, &params);
	if(err<0){
		//cerr << "Can't open file: " << input_filename << endl;
		exit(-1);
	}

	// Find the stream info.
	err = av_find_stream_info(fcx);

	// Find the first video stream.
	for(i=0; i<fcx->nb_streams; i++){
		ccx=fcx->streams[i]->codec;
		if(ccx->codec_type==CODEC_TYPE_AUDIO) break;
	}
	video_index=i;


	// Open stream.
	// FIXME: proper closing of av before exit.
	if(video_index>=0){

		codec = avcodec_find_decoder(ccx->codec_id);
		if(codec)
			err = avcodec_open(ccx, codec);
		if(err<0) {
			//cerr << "Can't open codec." << endl;
			exit(-1); 
		}
	}
	else{
		//cerr << "Video stream not found." << endl;
		exit(-1);
	}


	// Decode proper
	while(1){

		// Read a frame/packet.
		if(av_read_frame(fcx, &pkt) < 0 ) break;

		// If it's a video packet from our video stream... 
		if(pkt.stream_index==video_index){
			TRACE("pts = %I64d, dts = %I64d\n",pkt.pts, pkt.dts);

			// Decode the packet
			
			int16_t* pBuf = (int16_t*)av_malloc(336000);
			int data_size = 0;
			len1 = avcodec_decode_audio(ccx, pBuf, &data_size, pkt.data, pkt.size);
			//len1 = avcodec_decode_video(ccx, frame, &got_picture, 
			//	pkt.data, pkt.size);
			ReSampleContext* audioResamplectx = audio_resample_init(1,ccx->channels, ccx->sample_rate, ccx->sample_rate);
			uint8_t* pout = (uint8_t*)av_malloc(336000);
			int samnu = data_size/(ccx->channels*2);

			int lenr = audio_resample(audioResamplectx,(short*)pout, (short*)pBuf, samnu);

			if (got_picture) {

				// Allocate AVPicture the first time through.
				if(counter==0)
					avpicture_alloc(&pict, PIX_FMT_RGB24, 
					ccx->width, ccx->height);

				img_convert(&pict, PIX_FMT_RGB24, (AVPicture*) frame,
					ccx->pix_fmt, 
					ccx->width, ccx->height);

				// Visual effects: display image (save to disk, for now).
				//string name= "ss.ppm";
				//save_ppm(pict.data[0], ccx->width, ccx->height, 3, 
				//	name.c_str());

				counter++;		
			}

			av_free_packet(&pkt);
		}
	}

	// Clean up
	avpicture_free(&pict);
	av_free(frame);
	av_close_input_file(fcx);

}

void CFFDecoder::Test()
{
	AVFrame* pTemp = NULL;
	int size = 0;
	uint8_t* pbuf = NULL;
	int pmt = PIX_FMT_YUV420P;
	int w = 3;
	int h = 2;

	size = avpicture_get_size(pmt, w,h);
	pbuf = (uint8_t*)av_malloc(size);
	pTemp = avcodec_alloc_frame();
	avpicture_fill((AVPicture*)pTemp, pbuf, pmt, w, h);
	av_free(pTemp->data[0]);
	av_free(pTemp);



}


